package cn.com.bardream.myframework.servlet.v2;

import cn.com.bardream.myframework.annotation.MYAutowired;
import cn.com.bardream.myframework.annotation.MYController;
import cn.com.bardream.myframework.annotation.MYRequestMapping;
import cn.com.bardream.myframework.annotation.MYRequestParam;
import cn.com.bardream.myframework.annotation.MYService;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

public class MYDispatcherServlet extends HttpServlet {

    // 存储配置文件信息
    Properties configContext = new Properties();

    // 扫描到的类名存储容器
    List<String> classNameContext = new ArrayList<String>();

    // IOC容器 - key:beanName  value:Object
    Map<String, Object> ioc = new HashMap<String, Object>();

    // 采用map存储 HandlerMapping key:uri value:Method
    Map<String, Method> handlerMapping = new HashMap<String, Method>();

    // 先进行初始化
    @Override
    public void init(ServletConfig config) throws ServletException {
        //1、加载配置文件 -> 到configContext
        doLoadConfig(config.getInitParameter("contextConfigLocation"));
        //2、扫描相关的类->从configContext中获取需要扫描的包路径->classNameContext容器中
        doScanner(configContext.getProperty("scanPackage"));
        //3、初始化所有相关的类的实例，并且放入到IOC容器之中
        doInitIOC();
        //4、完成依赖注入
        doAutoWrite();
        //5、初始化HandlerMapping
        doInitHandlerMapping();

        System.out.println("MYframework is init.");
    }

    // 初始化HandlerMapping
    private void doInitHandlerMapping() {
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            if (!entry.getValue().getClass().isAnnotationPresent(MYController.class)) {
                continue;
            }
            MYRequestMapping annotation = entry.getValue().getClass().getAnnotation(MYRequestMapping.class);
            String baseUri = annotation.value();
            for (Method method : entry.getValue().getClass().getMethods()) {
                if (!method.isAnnotationPresent(MYRequestMapping.class)) {
                    continue;
                }
                MYRequestMapping anMapping = method.getAnnotation(MYRequestMapping.class);
                String methodUri = anMapping.value();
                String url = (baseUri + "/" + methodUri).replaceAll("/+", "/");
                handlerMapping.put(url, method);
            }

        }
    }

    // 将定义@AutoWrite的字段,赋值
    private void doAutoWrite() {
        try {
            for (Map.Entry<String, Object> entry : ioc.entrySet()) {
                Field[] fields = entry.getValue().getClass().getDeclaredFields();
                for (Field field : fields) {
                    if (!field.isAnnotationPresent(MYAutowired.class)) {
                        continue;
                    }
                    String beanName = field.getAnnotation(MYAutowired.class).value();
                    if ("".equals(beanName)) {
                        Class<?> type = field.getType();
                        beanName = type.getName();
                    }
                    if (!ioc.containsKey(beanName)) {
                        throw new Exception("the autowrite " + beanName + " is not found!");
                    }
                    Object object = ioc.get(beanName);
                    // 设置私有属性赋值,权限
                    field.setAccessible(true);
                    // 这就是自动注入最后的结果
                    field.set(entry.getValue(), object);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 从类名容器中获取全类名
    // 将进行@MYController @MYService 标记的标记类,通过反射实例化后,存入到ioc中
    private void doInitIOC() {
        try {
            for (String className : classNameContext) {
                Class<?> aClass = Class.forName(className);
                // 判断类上加了@MYController 注解的
                if (aClass.isAnnotationPresent(MYController.class)) {
                    String beanName = toLowFirstName(aClass.getSimpleName());
                    Object obj = aClass.newInstance();
                    ioc.put(beanName, obj);
                    // 判断类上加了@MYService 注解的
                } else if (aClass.isAnnotationPresent(MYService.class)) {
                    String beanName = toLowFirstName(aClass.getSimpleName());
                    MYService annotation = aClass.getAnnotation(MYService.class);
                    if (annotation.value() != "") {
                        beanName = annotation.value();
                    }
                    Object obj = aClass.newInstance();
                    ioc.put(beanName, obj);

                    // 将Service所实现的接口的全类名,也加载进IOC容器中
                    // 这样后面我们使用@MYAotuwrite是,就可以根据接口类型,去获取其实现类的实例
                    for (Class clazz : aClass.getInterfaces()) {
                        if (ioc.containsKey(clazz.getName())) {
                            throw new Exception("the class " + clazz.getName() + " is exist!");
                        }
                        ioc.put(clazz.getName(), obj);
                    }

                } else {
                    continue;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 将类名首字母转成小写
    private String toLowFirstName(String simpleName) {
        char[] chars = simpleName.toCharArray();
        if ('A' <= chars[0] && chars[0] <= 'Z') {
            chars[0] += 32;
        }
        return String.valueOf(chars);
    }

    // 获取配置文件中的需要扫描的包路径
    // 将包路径下所有的类名扫描到类名容器中
    private void doScanner(String scanPackage) {
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
        File filePath = new File(url.getFile());
        File[] files = filePath.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                doScanner(scanPackage + "." + file.getName());
            } else {
                if (!file.getName().endsWith(".class")) {
                    continue;
                }
                // 全类名
                String className = scanPackage + "." + file.getName().replace(".class", "");
                classNameContext.add(className);
            }
        }
    }

    // 去加载配置文件
    private void doLoadConfig(String contextConfigLocation) {

        InputStream inputStream = null;
        try {
            inputStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
            configContext.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doDispatcher(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doDispatcher(req, resp);
    }

    // 通过Dispatcher进行请求转发以及匹配
    private void doDispatcher(HttpServletRequest req, HttpServletResponse resp) {
        try {
            String requestURI = req.getRequestURI();
            if (!handlerMapping.containsKey(requestURI)) {
                resp.getWriter().write("404 not found!");
                return;
            }
            Method method = handlerMapping.get(requestURI);
            // 请求参数列表  key:paramName value:paramValue
            Map<String, String[]> parameterMap = req.getParameterMap();
            // 形参列表
            Class<?>[] parameterTypes = method.getParameterTypes();
            // 新建需要传入方法中的参数
            Object[] objs = new Object[parameterTypes.length];
            // 获取方法上的注解--[]参数位置,[]参数上注解数组
            Annotation[][] parameterAnnotations = method.getParameterAnnotations();
            for (int i = 0; i < parameterTypes.length; i++) {
                Class<?> parameterType = parameterTypes[i];
                if (parameterType == HttpServletRequest.class) {
                    objs[i] = req;
                } else if (parameterType == HttpServletResponse.class) {
                    objs[i] = resp;
                } else {
                    // 获取参数上的注解数组
                    Annotation[] parameterAnnotation = parameterAnnotations[i];
                    for (Annotation annotation : parameterAnnotation) {
                        if (!(annotation instanceof MYRequestParam)) {
                            continue;
                        }
                        MYRequestParam myRequestParam = (MYRequestParam) annotation;
                        // myRequestParam.value()不想做非空判断了,默认有值了
                        String[] strings = parameterMap.get(myRequestParam.value());
                        objs[i] = conver(parameterType,strings);
                    }
                }
            }
            Object o = ioc.get(toLowFirstName(method.getDeclaringClass().getSimpleName()));
            Object result = method.invoke(o, objs);
            if (result==null||result==Void.class){return;}
            resp.getWriter().write(result.toString());
            return;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private Object conver(Class<?> parameterType, String[] strings) {
        if (parameterType==String.class){
            return Arrays.toString(strings).replaceAll("\\[|\\]","").trim().replaceAll("\\s[2]",",");
        }
        if (parameterType==Integer.class){
            return Integer.valueOf(strings[0]);
        }
        if (parameterType==Double.class){
            return Double.valueOf(strings[0]);
        }
        return null;
    }

}
